/**
 * Groupe :14 Auteurs : Quentin Plomteux Cédric Jacobus Jean-Benoit Delbrouck
 * 
 * 
 * Simulation d'un routeur utilisant le routage par liens d'etat
 * 
 * Cours : Télécom, IPL 2, 2013
 * 
 */
class RTools14 {

	private final int mr; // nbr max routeur sur le retour sur le reseau
	private final int a; // numero du routeur
	private final int am; // nbr maximum de connexion à ce routeur
	private final int[] lc; // couts associé au different interface pour
							// joindre
	// chaque autre routeau
	private int seq; // le numero du dernier LSP envoyer

	private final LSP[] NDB; // neighbour database : Une base de
	// données reprenant des
	// informations concernant les
	// routeurs voisins. l'index represent le numero
	// du lien sur le routeur
	private final LSP[] LSDB;// link state database :Une base de
	// données de paquets de type
	// “link-state packet"; key : le
	// numero du routeau, value son LSP. l'index
	// represent le numero du routeur dans le reseau

	public static final int ROUTE_INCONNUE = Integer.MAX_VALUE;// cout si
																// l'interface
																// n'est pas
																// utilise

	public static final int AGE_MAX = 3;
	public static final int AGE_MIN = 0;// L'age minimal que doit avoir un lsp

	// possible d'avoir
	// plus d'une
	// interface qui
	// mene à un routeur
	// determine

	// pour pouvoir etre transmis

	/**
	 * Constructeur
	 * 
	 * @param Mr
	 *            nombre maximum de routeur sur le réseau
	 * @param a
	 *            numero du routeur
	 * @param am
	 *            nombre de lien sur le routeur
	 * @param lc
	 *            tableau des couts associer aux interfaces du routeur. vaut
	 *            Integer.MAX_VALUES si inutilise
	 */
	RTools14(final int Mr, final int a, final int am, final int[] lc) {
		if (Mr <= 1) {
			throw new IllegalArgumentException("Mr : Mr <= 1");
		}

		this.mr = Mr;

		if (a < 0 || a >= this.mr) {
			throw new IllegalArgumentException("a : ! 0 <= a < Mr");

		}

		this.a = a;

		// mr - 1 car il y a maximum mr-1 autre routeur sur le reseau
		if (am < 0 || am >= (this.mr - 1)) {
			throw new IllegalArgumentException("am : ! 0 <= am < Mr-1");
		}

		this.am = am;

		if (lc == null) {
			throw new IllegalArgumentException("lc : null");
		}

		if (lc.length != this.am) {
			throw new IllegalArgumentException("lc : lc.lenght != am");
		}

		for (int i = 0; i < am; i++) {
			if (lc[i] <= 0) {
				throw new IllegalArgumentException("lc : le " + i
						+ "em lien n'est pas valide");
			}
		}

		this.lc = lc;

		this.LSDB = new LSP[this.mr];
		this.NDB = new LSP[this.am];
		this.seq = 0;
		// Envoi un message au voisin pour dire qu'on s'est creer
		flood();

		// DONE
		// Hypothèses 0 ≤ a < Mr, 0 ≤ am, et lc != null. De plus, lc.length
		// =
		// am, et pour tout i appartenant à [0..am[ : 0 < lc[i]
		// Résultat Une nouvelle instance de la classe RToolsXX est créée
		// telle
		// que :
		// – LSDB est vide, et
		// – NDB est vide, et
		// – sa constante Mr vaut la valeur du paramètre Mr, et
		// – sa constante a vaut la valeur du paramètre a, et
		// – sa constante am vaut la valeur du paramètre am, et
		// – sa fonction de coût fc respecte la propriété suivante : pour
		// tout
		// i appartenant à [0..am[, si lc[i] != ROUTE_LIBRE alors fc(i) =
		// lc[i]
	}

	/**
	 * 
	 * Cree le lsp du routeur
	 * 
	 * */
	private LSP createLSP() {

		// Associe un cout a chaque voisin
		final LSP lsp = new LSP(this.a, AGE_MAX, this.seq, this.lc);
		this.seq++;
		return lsp;
	}

	/**
	 * Envoi un parquet Hello aux routeurs branche sur l'interface du routeur
	 */
	void flood() {
		for (int i = 0; i < am; i++) {
			send(i);
		}
		// Done
		// Résultat Un paquet de type Hello est transmis à tous les voisins de
		// R.
	}

	/**
	 * Transmet le lsp aux routeurs de NDB
	 * 
	 * @param lsp
	 *            le lsp a transmettre
	 */
	void flood(final LSP lsp) {
		if (lsp == null) {
			throw new IllegalArgumentException("lsp null");
		}

		// check l'age du lsp avant de le transmettre
		if (lsp.age <= AGE_MIN) {
			System.err.println("Essaie de transmission d'un LSP trop agé");
			return;
		}

		// Pour chacune des interface du routeur
		for (int i = 0; i < this.am; i++) {
			if (NDB[i] != null) {
				// On ne retourne pas un lsp a son emmeteur
				if (NDB[i].a != lsp.a) {
					send(i, lsp);
				}
			}
		}
		// Done
		// Hypothèses lsp != null
		// 4Résultat Le paquet intitulé lsp est transmis à tous les voisins
		// de
		// R.
	}

	/**
	 * 
	 * @return matrice d’adjacence d’un graphe modélisant le réseau des
	 *         routeurs
	 */
	int[][] links() {

		final int[][] t = new int[this.mr][this.mr];

		final int[] tabNonJoignable = new int[this.mr];// Aucun LSP pour ce
														// routeur
		for (int i = 0; i < this.mr; i++) {
			tabNonJoignable[i] = ROUTE_INCONNUE;
		}

		for (int i = 0; i < this.mr; i++) {
			if (LSDB[i] != null) {
				t[i] = LSDB[i].links.clone();
			} else {
				if (i == this.a) {
					// Si on est sur nous même
					t[i] = lc.clone();
				}

				t[i] = tabNonJoignable.clone();
			}
		}

		// Verification : Si un lien est connu dans un sens mais pas dans
		// l'autre probleme, on considere que le lien n'existe pas
		for (int i = 0; i < this.mr; i++) {
			for (int j = i; i < this.mr; j++) {
				if (i == j) {
					// Route vers soit meme cout null
					t[i][j] = 0;
				} else if (t[i][j] != t[j][i]) {
					t[i][j] = ROUTE_INCONNUE;
					t[j][i] = ROUTE_INCONNUE;
				}
			}
		}

		return t;

		// Résultat Un tableau carré de “int” que l’on nommera t dans la
		// suite
		// de ce paragraphe. Plus particulièrement, t est un tableau à deux
		// dimensions dont la taille de la première et la seconde dimension
		// est Mr. De plus, pour tout i,j ∈ [0..Mr[ :
		// (a) si R a détecté que les deux routeurs i et j sont connectés
		// ensemble à l’aide d’un lien ayant un coût lc alors t[i][j] =
		// lc,
		// sinon
		// (b) t[i][j] = ROUTE_INCONNUE.
		// Nous remarquerons que le tableau t peut être interprété comme la
		// matrice d’adjacence d’un graphe modélisant le réseau des
		// routeurs.
		// Ce graphe est construit à partir de la base de données LSDB.
	}

	/**
	 * Verifie si l est un LSP a retenir
	 * 
	 * @param l
	 *            le lsp
	 * @return true si le lsp est nouveau ou plus recent que celui conteni dans
	 *         LSDB
	 */
	boolean newer(final LSP l) {
		if (l == null) {
			throw new IllegalArgumentException("l null");
		}

		if (LSDB[l.a] == null) {
			return true;
		}

		if (l.seq > LSDB[l.a].seq) {
			return true;
		}

		return false;

		// Done
		// Hypothèses l1 != null.
		// Résultat
		// – true si LSDB ne contient pas de LSP relatif au routeur l.a, et
		// – true si LSDB contient un LSP, relatif au routeur l.a, qui a un
		// numéro de séquence plus petit que l.seq, et
		// – faux sinon.
	}

	/**
	 * Met à jour la table NDB
	 * 
	 * @param i
	 *            interface du routeur vers le routeur V
	 * @param V
	 *            routeur voisin utilisant l'interface i pour communiquer avec
	 *            le routeur
	 */
	void receiveHelloMessage(final int i, final int V) {
		if (i < 0 || i >= this.am) {
			throw new IllegalArgumentException(
					"i n'est pas un routeur valide du reseau");
		}

		if (V < 0 || V >= this.am) {
			throw new IllegalArgumentException(
					"V n'est pas un routeur valide du reseau");
		}

		// Si c'est un nouveau
		if (NDB[i] == null) {
			// Pas la methode optimal mais la plus pragmatique
			NDB[i] = new LSP(V, AGE_MAX, 0, null);

			// Met a jour la table des cout pour atteindre les voisins
			// Si l'interface est libre on lui associe le cout
			if (lc[i] == ROUTE_INCONNUE) {
				// IMPOSSIBLE A FAIRE CAR ON NE CONNAIT PAS LE COUT DE LA
				// LIAISON
			}

			// Si c'est un nouveau lien il faut lui repondre
			send(i);
		} else {
			NDB[i].age = AGE_MAX;
			NDB[i].a = V;
		}

		// verifier s'il y a un changement d'interface
		for (int j = 0; j < am; j++) {
			if (NDB[j] != null) {
				if (NDB[j].a == V && j != i) {
					// Il y a une autre interface que i qui est connecter a
					NDB[j] = null;
					// ? break;
				}
			}
		}

		// DONE
		// Hypothèses 0 ≤ i < am et 0 ≤ V < Mr
		// Résultat les informations concernant le routeur V sont mises à jour
		// dans la base de données NDB. En particulier, le temps de vie
		// résiduel du routeur V vaut AGE_MAX et l’adresse du lien qui
		// connecte
		// R à
		// V vaut i.
	}

	/**
	 * 
	 * @param lsp
	 */
	void receiveLSP(final LSP lsp) {
		if (lsp == null) {
			throw new IllegalArgumentException("le lsp est null");
		}

		// On nous a envoye notrele lsp => probleme
		if (lsp.a == this.a) {
			throw new InternalError(
					"Reception d'un LSP qu'on a emmit soit meme");
		}

		// Paquet périmer on l'ignore
		if (lsp.age <= AGE_MIN) {
			System.err.println("reception d'un paquet perime");
			return;
		}

		if (newer(lsp)) {
			// mettre a jour le LSDB
			LSDB[lsp.a] = lsp;

			// PROBLEME IL FAUDRAIT CONNAITRE LA PERSONNE QUI NOUS TRANSMIT LE
			// LSP POUR PAS LUI RETRANSMETTRE
			flood(lsp);
		}
		// DONE
		// Hypotheses lsp != null.
		// Resultat Si necessaire LSDB est mis à jour et le paquet intitule lsp
		// est retransmis à tous les voisins de R.
	}

	/**
	 * Envoi un paquet de type Hello sur l'interface i
	 * 
	 * @param i
	 *            l'interface du serveur
	 */
	void send(final int i) {
		// Vous ne devez pas implementer cette methode

		// Hypothèses 0 ≤ i < am
		// Résultat Si R possède un lien connecté à l’interface ayant
		// l’adresse
		// i,
		// un paquet de type Hello est envoyé sur cette interface. Dans le
		// cas contraire aucun paquet n’est envoyé.
	}

	/**
	 * transmet lsp sur cette l'interface i
	 * 
	 * @param i
	 *            l'interface du routeur
	 * @param lsp
	 *            le lsp
	 */
	void send(final int i, final LSP lsp) {
		// Vous ne devez pas implementer cette methode

		// Hypothèses 0 ≤ i < am et lsp != null
		// Résultat Si R possède un lien connecté à l’interface ayant
		// l’adresse
		// i,
		// le paquet lsp est envoyé sur cette interface. Il n’est pas envoyé
		// dans le cas contraire.

	}

	void tick() {

		// veillissement NDB et check des interfaces
		for (int i = 0; i < am; i++) {
			if (NDB[i] != null) {
				NDB[i].age--;
				// Si l'interface est a jour
				if (NDB[i].age <= AGE_MIN) {
					NDB[i] = null;
				}
			}
		}

		flood();

		// TODO verifier la logique

		// veillissement LSDB
		for (int i = 0; i < mr; i++) {
			if (LSDB[i] != null && LSDB[i].a != this.a) {
				// Si il y un LS qui va vers i et que n'est pas nous
				LSDB[i].age--;
				if (LSDB[i].age <= AGE_MIN) {
					LSDB[i] = null;
				}
			}
		}

		// genere le LSP et l'envoi aux NDB
		flood(createLSP());

		// Résultat Les actions suivantes ont été opérées :
		// (a) a a envoyé un paquet de type LSP à tous ses voisins.
		// (b) a a envoyé un paquet de types Hello à tous ses voisins.
		// (c) Les durées de vie résiduelles des LSPs présents dans LSDB
		// ont été décrémentées. De plus, les LSPs dont la durée de vie
		// résiduelle résultante vaut 0 ont été supprimés.
		// (d) Les durées de vie résiduelles des messages de type Hello
		// présents dans NDB ont été décrémentées. De plus, les routeurs
		// voisins qui sont associés à un paquet de type Hello dont la
		// durée de vie résiduelle résultante vaut 0 ont été supprimés de
		// NDB.
	}
}